﻿using System.Collections.Generic;
using BBCodeParser.Nodes;
using BBCodeParser.Tags;

namespace BBCodeParser
{
	public class BbParser : IBbParser
	{
		private readonly Tag[] tags;
		private readonly Dictionary<string, string> substitutions;
		private const int TreeMaxDepth = 6000;

		public BbParser(Tag[] tags, Dictionary<string, string> substitutions)
		{
			this.tags = tags;
			this.substitutions = substitutions;
		}

		public NodeTree Parse(string input)
		{
			var nodeTree = new NodeTree(substitutions);
			var treeDepth = 0;
			if (string.IsNullOrWhiteSpace(input))
			{
				nodeTree.AddChild(new TextNode(string.Empty));
				return nodeTree;
			}

			var reader = new Reader(input, tags);
			var current = (Node) nodeTree;
			TagResult tagResult;
			if (!reader.TryRead(out tagResult))
			{
				current.AddChild(new TextNode(input));
			}
			else
			{
				do
				{
					if (!string.IsNullOrEmpty(tagResult.Text))
					{
						current.AddChild(new TextNode(tagResult.Text));
					}

                    // no parsing inside CodeTag
				    var isInsideCodeTag = (current as TagNode)?.Tag is CodeTag;
				    var resultIsClosingCodeTag = tagResult.Tag is CodeTag && tagResult.TagType == TagType.Close;
				    if (isInsideCodeTag && !resultIsClosingCodeTag && !string.IsNullOrEmpty(tagResult.Match))
				    {
				        current.AddChild(new TextNode(tagResult.Match));
				        continue;
				    }
				    switch (tagResult.TagType)
					{
						case TagType.NoResult:
							continue;
						case TagType.Open:
							var tagNode = new TagNode(tagResult.Tag, current, tagResult.AttributeValue);
							current.AddChild(tagNode);

							if (!tagResult.Tag.RequiresClosing) continue;

							current = tagNode;
							treeDepth++;
							if (treeDepth > TreeMaxDepth)
							{
								throw new BbParserException();
							}
							break;
						default:
							if (tagResult.TagType == TagType.Close && current != nodeTree)
							{
							    var currentTagNode = (TagNode) current;
							    if (currentTagNode.Tag.Name != tagResult.Tag.Name)
							    {
							        do
							        {
							            current = current.ParentNode;
							            treeDepth--;
							        } while (current != nodeTree && currentTagNode.Tag.Name != tagResult.Tag.Name);
							    }
							    else
							    {
							        current = current.ParentNode;
                                    treeDepth--;
							    }
							}
							break;
					}
				} while (reader.TryRead(out tagResult));
			}
			return nodeTree;
		}
	}
}